/*------------------------------------------------------------------------------
 *  SNode - Schematic Node class.  Major objects in the canvas where edges
 *  meet.  For example, in a circuit drawing program, the nodes would be the
 *  circuit elements (gates, resistors, etc.) and edges would be wires.  
 *  
 *  Canvas node objects can have terminals where edges can terminate (node 
 *  terminals would be analogous to the 'pins' on a digital gate where wires 
 *  attach) or the node itself can be used to terminate edges (as in traditional 
 *  graph theory).
 *  
 *  Graphically, a canvas node object is just a collection of line segments that
 *  draw the desired shape of the object
 *  
 *  August 2009, Forhad Ahmed
 *------------------------------------------------------------------------------
 */

package schematic;

import geometry.CLine;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.Point2D;
import java.util.Vector;

import vector.vector;

public class SNode extends SObject {
    /*
     * Constructor
     */
    public SNode() {
        super();
        initNode();
    }
    
    
    /*
     * Explicit-id constructor
     */
    public SNode(int i) {
        super(i);
        //initNode();
    }
    
    
    /*
     * Initialize the node data structures
     */
    public void initNode() {
        center = new Point();
        bounds = new Polygon();
        body = new vector("node-body");
        terminals = new vector("node-terminals");
        suggest_terminal = null;
        iline = new CLine();
        ipoint = new Point();
        old_connections = null;
    }
    
    
    /*
     * 
     */
    public SNode clone() {
        int x, y, index;
        SNode node;
        SNodeTerminal terminal, new_terminal;
        CLine line;
        
        node = new SNode();
        
        /*
         * Center point
         */
        node.center.x = center.x;
        node.center.y = center.y;
        
        /*
         * Body points
         */
        for (index = 0; index < body.size(); index++) {
            line = (CLine) body.get(index);
            node.body.add(line.clone());
        }
        
        /*
         * Bounding box
         */
        for (index = 0; index < bounds.npoints; index++) {
            x = bounds.xpoints[index];
            y = bounds.ypoints[index];
            node.bounds.addPoint(x, y);
        }
        
        /*
         * Terminals
         */
        for (index = 0; index < terminals.size(); index++) {
            terminal = (SNodeTerminal) terminals.get(index);
            x = terminal.p.x; y = terminal.p.y;
            new_terminal = new SNodeTerminal(x, y, node);
            node.terminals.add(new_terminal);
        }
        
        /*
         * Properties
         */
        node.clip_edges = clip_edges;
        
        return node;
    }
    
    
    /*
     * Return a copy of the object to be used for checkpointing
     */
    public SObject checkpoint(int action) {
        SNode checkpoint;
        
        checkpoint = new SNode(id);
        checkpoint.original = this;
        /*
         * Only calculate copies of internal parts if its a mod operation
         */
        if (action == Schematic.CHECKPOINT_MOD) {
            
            checkpoint.center = new Point(center.x, center.y);
            checkpoint.old_connections = getConnections();
            checkpoint.rotation = this.rotation;
        }
        
        return checkpoint;
    }
    
    
    /*
     * Revert this modified object back to it's checkpointed state
     */
    public void revert(SObject object) {
        SNode checkpoint;
        SNodeTerminal terminal;
        vector terminal_edges;
        int index, cx, cy, dx, dy;
        checkpoint = (SNode) object;
        
        /*
         * Move the center back to the checkpointed point and then translate the
         * bounds and terminals too
         */
        cx = center.x; cy = center.y;
        center.x = checkpoint.center.x;
        center.y = checkpoint.center.y;
        dx = center.x - cx;
        dy = center.y - cy;
        bounds.translate(dx, dy);
        translateTerminals(dx, dy);
        
        /*
         * Revert the terminal->edge mappings
         */
        setConnections(checkpoint.old_connections);
    }
    
    
    /*
     * Given a vector of terminal-edge mappings, set the node's terminals' edges
     */
    protected void setConnections(vector connections) {
        int index;
        SNodeTerminal terminal;
        vector terminal_edges;
        /*
         * change the terminal->edge mappings
         */
        for (index = 0; index < connections.size(); index++) {
            /*
             * Get the old terminal-edge map
             */
            terminal_edges = (vector) connections.get(index);
            
            /*
             * Get the current object's terminal.
             */
            terminal = (SNodeTerminal) terminals.get(index);
            
            /*
             * Revert the terminal's edge map
             */
            terminal.edges = terminal_edges;
        }        
    }
    
    
    /*
     * Make a summarized copy of the terminals connections.
     */
    public vector getConnections() {
        vector connections = new vector("node-connections");
        SNodeTerminal terminal;
        vector terminal_edges;
        int index;
        
        for (index = 0; index < terminals.size(); index++) {
            terminal = (SNodeTerminal) terminals.get(index);
            terminal_edges = terminal.edges.clone();
            connections.add(terminal_edges);
        }
        
        return connections;
    }
    
    
    /*
     * Given a node object, see if it has the same edge map.  Since 'this' is a
     * checkpointed object, we will use the old_connections map to check against
     * the original object's terminals
     */
    public boolean sameConnections(SNode original) {
        int i, j;
        SNodeTerminal terminal;
        vector oedges, cedges;
        SEdge oedge, cedge;
        
        if (old_connections.size() != original.terminals.size()) {
            return false;
        }
        
        for (i = 0; i < original.terminals.size(); i++) {
            terminal = (SNodeTerminal) original.terminals.get(i);
            oedges = (vector) terminal.edges;
            cedges = (vector) old_connections.get(i);
            
            if (oedges.size() != cedges.size()) {
                return false;
            }
            
            for (j = 0; j < oedges.size(); j++) {
                oedge = (SEdge) oedges.get(j);
                cedge = (SEdge) cedges.get(j);
                
                if (oedge != cedge) {
                    return false;
                }
            }
        }
        
        return true;
    }
    
    
    /*
     * 
     */
    public SObject diff(SObject node) {
        SNode original;
        
        original = (SNode) node;
        
        old_connections = this.old_connections;
        new_connections = null;
        
        if (checkpoint == Schematic.CHECKPOINT_MOD) {
            center.x = original.center.x - center.x;
            center.y = original.center.y - center.y;
            
            if (!sameConnections(original)) {
                old_connections = this.old_connections;
                new_connections = original.getConnections();
            } else {
                old_connections = null;
                new_connections = null;
            }
            
            if (rotation != original.rotation) {
                rotation = original.rotation - rotation;
            } else {
                rotation = 0;
            }
        }

        return this;
    }
    
    
    /*
     * 
     */
    public void rpatch(SObject object, Schematic schematic) {
        SNode diff = (SNode) object;
        
        if (diff.checkpoint == Schematic.CHECKPOINT_MOD) {
            move(-diff.center.x, -diff.center.y);
            if (diff.old_connections != null) {
                setConnections(diff.old_connections);
            }
            if (diff.rotation != 0) rotate(-diff.rotation);
        } else if (diff.checkpoint == Schematic.CHECKPOINT_ADD) {
            schematic.remove_internal(diff.original);
        } else if (diff.checkpoint == Schematic.CHECKPOINT_DEL) {
            schematic.add_internal(diff.original);
        }
    }
    
    
    /*
     *
     */
    public void patch(SObject object, Schematic schematic) {
        SNode diff = (SNode) object;
        
        if (diff.checkpoint == Schematic.CHECKPOINT_MOD) {
            move(diff.center.x, diff.center.y);
            if (diff.new_connections != null) {
                setConnections(diff.new_connections);
            }
            if (diff.rotation != 0) rotate(diff.rotation);
        } else if (diff.checkpoint == Schematic.CHECKPOINT_ADD) {
            schematic.add_internal(diff.original);
        } else if (diff.checkpoint == Schematic.CHECKPOINT_DEL) {
            schematic.remove_internal(diff.original);
        }
        
    }
    
    
    /*
     * Get the dependent edges of this node.  Edges have to be connected either
     * vertically or horizontally in order to be a dependent of this node (coz
     * being attached vert. or horiz. to this node means that the edge will be
     * modified as this node moves.
     */
    public Vector dependents() {
        int i, j;
        SEdge edge;
        SNodeTerminal terminal;
        
        dependents.clear();
        
        for (i = 0; i < terminals.size(); i++) {
            terminal = (SNodeTerminal) terminals.get(i);
            for (j = 0; j < terminal.edges.size(); j++) {
                edge = (SEdge) terminal.edges.get(j);
                if (!dependents.contains(edge)) {
                    dependents.add(edge);
                }
            }
        }
        
        return dependents;
    }
    
    
    /*
     * Calculate the center of mass for this node object by calculating the 
     * center of mass of it's bounds points
     */
    public Point2D.Float center() {
        int x, y, index;
        
        com.x = 0; com.y = 0;
        
        for (index = 0; index < bounds.npoints; index++) {
            x = bounds.xpoints[index];
            y = bounds.ypoints[index];   
            com.x += x; com.y += y;
        }
        
        com.x /= (float)bounds.npoints;
        com.y /= (float)bounds.npoints;
        
        return com;
    }
    
    
    /*
     * Set the color for this node.  If color is not set, default color will be
     * used to draw the node.
     */
    public void setColor(Color c) {
        this.color = c;
    }
    
    
    /*
     * Add a line segment to the body of the node. Offset from (0,0)
     */
    public void addLine(int x1, int y1, int x2, int y2) {
        body.add(new CLine(x1, -y1, x2, -y2));
    }
    
    
    /*
     * Add a terminal to the node. Offset from (0,0)
     */
    public void addTerminal(int x, int y) {
        terminals.add(new SNodeTerminal(x , -y, this));
    }
    
    
    /*
     * Add a line segment that represents the bounding polygon for this node
     */
    public void addBoundSegment(int x, int y) {
        bounds.addPoint(x, -y);
    }
    
    
    /*
     * If the bounding polygon for this node is a rectangle, then use this API
     * to simplify the creation of the bounds polygon.
     */
    public void setBoundRectangle(int x, int y, int w, int h) {
        bounds.reset();
        bounds.addPoint(x, -y);
        bounds.addPoint(x + w, -y);
        bounds.addPoint(x + w, -y + h);
        bounds.addPoint(x, -y + h);
    }
    
    
    /*
     * Set the position of this object in canvas coordinates
     */
    public Point place(int x, int y) {
        int cx, cy, dx, dy;
        cx = center.x; cy = center.y;
        dx = x - hit_context.x; dy = y - hit_context.y;
        center.x += dx; center.y += dy;
        bounds.translate(dx, dy);
        translateTerminals(dx, dy);
        d.x = dx; d.y = dy;
        hit_context.x += dx; hit_context.y += dy;
   
        return d;
    }
    
    
    /*
     * Translate/move this object in canvas coordinates
     */
    public void move(int dx, int dy) {
        center.x += dx; center.y += dy;
        bounds.translate(dx, dy);
        translateTerminals(dx, dy);
    }
    
    
    /*
     * Helper function to translate the terminals of this object as the object 
     * is moved
     */
    protected void translateTerminals(int dx, int dy) {
        int index;
        SNodeTerminal t;
        
        for (index = 0; index < terminals.size(); index++) {
            t = (SNodeTerminal) terminals.get(index);
            t.translate(dx, dy);
        }
    }
   
    
    /*
     * Rotate the node about a point P.  First rotate the center about P.  Then 
     * rotate the node body/bounds/terminals about the center.  Note that the 
     * order of these two operations may be swapped.
     */
    public void rotate(int dir, int x, int y) {
        int cos, sin;
        int cx, cy, tx, ty, ox, oy, dx, dy;
        
        cos = (int) Math.round(Math.cos(dir * Math.PI/2));
        sin = (int) Math.round(Math.sin(dir * Math.PI/2));
        
        /*
         * First, rotate the center around P;
         */
        cx = center.x; cy = center.y; ox = cx; oy = cy;
        cx -= x; cy -= y; tx = cx; ty = cy; 
        cx = tx * cos + ty * sin;  cy = -tx * sin + ty * cos;
        cx += x; cy += y; dx = cx - ox; dy = cy - oy;
        move(dx, dy);
        
        /*
         * Then rotate the node itself around the center
         */
        rotate(dir);
    }
    
    
    /*
     * Rotate the object given a rotation angle delta (multiple of pi/2).
     */
    public void rotate(int dir) {
        int cos, sin;
        CLine line;
        SNodeTerminal terminal;
        int index;
        int x1, y1, x2, y2, tx, ty, x, y;
        int xpoints[], ypoints[], npoints;
        
        rotation = (rotation + dir) % 4;
        
        cos = (int) Math.round(Math.cos(dir * Math.PI/2));
        sin = (int) Math.round(Math.sin(dir * Math.PI/2));
        
        /*
         * First, rotate the body around (0,0)
         */
        for (index = 0; index < body.size(); index++) {
            line = (CLine) body.get(index);
            x1 = line.x1; y1 = line.y1;  x2 = line.x2; y2 = line.y2;
            line.x1 = x1 * cos + y1 * sin; line.y1 = -x1 * sin + y1 * cos;
            line.x2 = x2 * cos + y2 * sin; line.y2 = -x2 * sin + y2 * cos;
        }
        
        /*
         * Rotate bounds about the center. First move back to origin.  Then do
         * the rotation.  Then move back to center.
         */
        xpoints = bounds.xpoints.clone(); ypoints = bounds.ypoints.clone();
        npoints = bounds.npoints; bounds.reset();
        for (index = 0; index < npoints; index++) {
            x = xpoints[index]; y = ypoints[index];
            x -= center.x; y -= center.y; tx = x; ty = y;
            x = tx * cos + ty * sin; y = -tx * sin + ty * cos;
            x += center.x; y += center.y;
            bounds.addPoint(x, y);
        }
        
        /*
         * Rotate the terminals about center.
         */
        for (index = 0; index < terminals.size(); index++) {
            terminal = (SNodeTerminal) terminals.get(index);
            x = terminal.p.x; y = terminal.p.y;
            x -= center.x; y -= center.y; tx = x; ty = y;
            x = tx * cos + ty * sin; y = -tx * sin + ty * cos;
            x += center.x; y += center.y;
            terminal.p.x = x; terminal.p.y = y;
        } 
    }
    
    
    /*
     * Check if the object is a hit given a screen coord
     */
    public boolean hit(int sx, int sy) {
        int cx, cy;
        
        cx = Schematic.s2c(sx);
        cy = Schematic.s2c(sy);
        
        if (bounds.contains(cx, cy)) {
            return true;
        }
        return false;
    }
    
    
    /*
     * Set the hit context for this canvas node.  For canvas nodes, the context
     * is the difference between the node center and the currently clicked point
     */
    public void setHitContext(int cx, int cy) {
        hit_context.x = cx;
        hit_context.y = cy;
    }
    
    
    /*
     * Given screen coords, see if this node contains any terminals that fall 
     * near that screen coord.  If any terminals do fall near that point, then
     * we cache that terminal for later use
     */
    public SNode suggest(int sx, int sy) {
        int index;
        SNodeTerminal t;
        
        suggest_terminal = null;
        
        for (index = 0; index < terminals.size(); index++) {
            t = (SNodeTerminal) terminals.get(index);
            spoint.x = Schematic.c2s(t.p.x);
            spoint.y = Schematic.c2s(t.p.y);
            if (spoint.distance((double)sx, (double)sy) < 10) {
                suggest_terminal = t;
                return this;
            }
        }
        return null;
    }
    
    
    /*
     * Nodes are always selectable!
     */
    public boolean selectable() {
        if (!mark_selected) return false;
        return true;
    }
    
    
    /*
     * Get the bounding box in canvas coordinates for this node
     */
    public Rectangle getBoundingBox() {
        return bounds.getBoundingBox();
    }
    
    
    /*
     * Given a line segment in screen coordinates, find the intersection point
     * of that line segment with any of the bounds line segments
     */
    public Point boundsIntersection(int x1, int y1, int x2, int y2) {
        int index, sbx1, sby1, sbx2, sby2;
        Point2D.Double ip;
        
        for (index = 0; index < bounds.npoints; index++) {
            sbx1 = bounds.xpoints[index]; 
            sby1 = bounds.ypoints[index];
            sbx2 = bounds.xpoints[(index + 1) % bounds.npoints];
            sby2 = bounds.ypoints[(index + 1) % bounds.npoints];
            
            sbx1 = Schematic.c2s(sbx1); sby1 = Schematic.c2s(sby1); 
            sbx2 = Schematic.c2s(sbx2); sby2 = Schematic.c2s(sby2);
          
            iline.x1 = sbx1; iline.y1 = sby1;
            iline.x2 = sbx2; iline.y2 = sby2;
            
            if ((ip = iline.intersection(x1, y1, x2, y2)) != null) {
                ipoint.x = Math.round((float) ip.x);
                ipoint.y = Math.round((float) ip.y);
                return ipoint;
            }
        }      
        
        return null;
    }
    
    
    /*
     * Draw the terminal objects for this node
     */
    public void drawTerminals(Graphics g, Schematic schematic, Color c) {
        int index;
        SNodeTerminal t;
        int tx, ty;
        
        g.setColor(c);
        for (index = 0; index < terminals.size(); index++) {
            t = (SNodeTerminal) terminals.get(index);
            tx = Schematic.c2s(t.p.x);
            ty = Schematic.c2s(t.p.y);
            g.fillRect(tx - 1, ty - 1, 3, 3);
        }
    }
    
    
    /*
     * Draw the bounding box for a canvas object
     */
    public void drawBounds(Graphics g, Schematic schematic) {
        int index; 
        int x1, y1, x2, y2;
        
        if (schematic == null) return;
        
        g.setColor(schematic.bounds_color);
        for (index = 0; index < bounds.npoints; index++) {
            x1 = bounds.xpoints[index]; y1 = bounds.ypoints[index];
            x2 = bounds.xpoints[(index + 1) % bounds.npoints];
            y2 = bounds.ypoints[(index + 1) % bounds.npoints];
            
            x1 = Schematic.c2s(x1); y1 = Schematic.c2s(y1); 
            x2 = Schematic.c2s(x2); y2 = Schematic.c2s(y2);
          
            g.drawLine(x1, y1, x2, y2);
        }
    }
    
    
    /*
     * Draw a canvas node
     */
    public void draw(Graphics g, Schematic schematic) {
        int index;
        CLine line;
        Graphics2D g2d;

        g2d = (Graphics2D) g;
        
        if (aa) {
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 
                                 RenderingHints.VALUE_ANTIALIAS_ON);
        }
        
        if (draw_bounds) {
            drawBounds(g, schematic);
        }
        
        if (selected) {
            if (color != null) {
                /*
                 * Use inverse of object.color
                 */
            } else {
                g.setColor(schematic.selection_color);
            }
        } else {
            if (color != null) {
                g.setColor(color);
            } else {
                g.setColor(schematic.node_color);
            }
        }
        
        if (preview) {
            g.setColor(Color.gray);
        }
        
        for (index = 0; index < body.size(); index++) {
            if (body.get(index) instanceof CLine) {
                line = (CLine) body.get(index);
                
                /*
                 * Since the body of the object holds line segments that are 
                 * only offsets from center, we need to translate the segments 
                 * with the center point.  Also since the VCanvasObject stores 
                 * all its points in canvas coordinates, we have to convert to
                 * screen coordinates
                 */
                g.drawLine(Schematic.c2s(center.x + line.x1), 
                           Schematic.c2s(center.y + line.y1), 
                           Schematic.c2s(center.x + line.x2),
                           Schematic.c2s(center.y + line.y2));
            }
        }
        
        if (draw_terminals) {
            drawTerminals(g, schematic, g.getColor());
        }
    }
    
    
    /*
     * Sub-type of VCanvasObject
     */
    public String typeString() {
        return "[node]";
    }
    
    
    /*
     * Dump the node including its terminal-edge mappings 
     */
    public String dump() {
        int i, j;
        SNodeTerminal terminal;
        SEdge edge;
        
        String s = "";
        s += super.toString() + " ";
        s += "[" + center.x + "," + center.y + "]" + " ";
        s += "TERMINALS: ";
        for (i = 0; i < terminals.size(); i++) {
        s += "[ ";
        terminal = (SNodeTerminal) terminals.get(i);   
        for (j = 0; j < terminal.edges.size(); j++) {
        edge = (SEdge) terminal.edges.get(j);
        s += edge.id + ", ";
        }
        s += "]";
        }
        return s;
    }
    
    /*
     * center - Center point based on canvas coords.  Updated as object is moved
     * body   - body of the object in VLines.  Relative from (0,0). NOT updated 
     *          as object is moved. Updated for roation
     * bounds - Bounding polygon.  Updated for move. Updated for rotation
     * .
     * .
     */
    public Point     center;
    public vector    body;
    public Polygon   bounds;
    public vector    terminals;   
    public int       rotation = 0;
    
    /*
     * Used for holding old/new terminal-edge mappings.  If this object is a
     * checkpoint object, then only 'old_connections' is used.  If this object
     * is a diff object, then both old/new_connections are used.
     */
    public vector    old_connections;
    public vector    new_connections;
    
    /*
     * 
     */
    public SNodeTerminal suggest_terminal;
    public CLine         iline;
    public Point         ipoint;
    
    /*
     * Drawing options
     * 
     * clip_edges - Clip inbound edges on this node at the node bounds polygon.
     *              For good aesthetics, it is recommended that the node bounds
     *              be such that any inbound edges on any terminal will cross
     *              the bounds polygon exactly at one line segment of the 
     *              polygon.
     */
    public    boolean preview        = false;
    public    Color   preview_color  = new Color(70, 70, 70);
    public    boolean draw_bounds    = false;
    public    boolean draw_terminals = false;
    public    boolean clip_edges     = false;
}


/*
 * SNodeTerminal objects are part of schematic node objects and are used as
 * points where edges can be 'attached' onto schematic node objects.  If a node 
 * object were a digital chip, the terminal objects for that node would be like
 * the pins of that digital chip where wires are attached.
 */
class SNodeTerminal {
    /*
     * Constructor
     */
    public SNodeTerminal(int x, int y, SNode parent) {
        p = new Point(x, y);
        this.parent = parent;
        edges = new vector("terminal-edges");
    }
    
    
    /*
     * Translate the terminal point (used when parent node itself is translated)
     */
    public void translate(int dx, int dy) {
        p.translate(dx, dy);
    }
    
    
    /*
     * Dump object
     */
    public String dump() {
        String s = "";
        s += super.toString() + " ";
        s += "[" + Integer.toString(p.x) + "," + Integer.toString(p.y) + "]";
        return s;
    }
    
    
    /*
     * Type string
     */
    public String typeString() {
        return "[terminal]";
    }
    

    /*
     * 
     */
    public Point  p;
    public vector edges;
    public SNode  parent;
}

